package com.qf.cx.simplenms.reader.impl;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.qf.cx.simplenms.link.api.Link;
import com.qf.cx.simplenms.link.api.LinkManager;
import com.qf.cx.simplenms.link.api.LinkManager.ReaderSource;
import com.qf.cx.simplenms.link.model.OpResultSpec;
import com.qf.cx.simplenms.link.model.OpSpec;
import com.qf.cx.simplenms.link.model.TagResultSpec;
import com.qf.cx.simplenms.reader.model.AntennaModel;
import com.qf.cx.simplenms.reader.model.ReaderModel;
import com.qf.cx.simplenms.reader.spi.ReaderSpi;
import com.qf.cx.simplenms.reader.tag.AntennaTag;
import com.qf.cx.simplenms.reader.tag.ReaderTags;
import com.qf.cx.simplenms.reader.tag.Tag;
import com.qf.cx.simplenms.tagmemory.model.TagMemoryModel;

//  TODO 本类需要重新设计，需要考虑不同天线读取不同标签区
public abstract class AbstractReader extends Observable implements ReaderSpi {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	private final LinkManager linkManager;
	private Link link;
	private AtomicBoolean started = new AtomicBoolean(false);

	protected final ReaderModel readerModel;
	/**
	 * 缓存每个观察者所配置的标签区信息
	 */
	protected final Map<Observer, CopyOnWriteArraySet<TagMemoryModel>> observer2TagMemories = new ConcurrentHashMap<>();

	public AbstractReader(ReaderModel readerModel, LinkManager linkManager) {
		this.readerModel = readerModel;
		this.linkManager = linkManager;
	}

	@Override
	public void addTagMemories(Observer observer, Set<TagMemoryModel> news) {
		if (news.isEmpty()) {
			return;
		}
		news = new HashSet<>(news);
		logger.info("reader '{}' add tms {}", getReaderName(), news);

		observer2TagMemories.putIfAbsent(observer, new CopyOnWriteArraySet<>());
		Set<TagMemoryModel> olds = observer2TagMemories.get(observer);
		olds.addAll(news);
		restartOpIfReaderStarted();
	}

	@Override
	public void deleteTagMemories(Observer observer, Set<TagMemoryModel> deletes) {
		if (deletes.isEmpty()) {
			return;
		}
		deletes = new HashSet<>(deletes);
		logger.info("reader '{}' delete tms {}", getReaderName(), deletes);

		observer2TagMemories.putIfAbsent(observer, new CopyOnWriteArraySet<>());
		Set<TagMemoryModel> olds = observer2TagMemories.get(observer);
		olds.removeAll(deletes);
		restartOpIfReaderStarted();
	}

	@Override
	public void start(Observer observer) {
		super.addObserver(observer);
		if (!started.compareAndSet(false, true)) {
			logger.debug("reader '{}' already started", getReaderName());
			return;
		}
		logger.info("reader '{}' starting", getReaderName());
		this.link = linkManager.establish(this.readerModel.getReaderSource());
		this.link.addObserver(this);
		startOp();
		logger.info("reader '{}' started", getReaderName());
	}

	@Override
	public void stop(Observer observer) {
		observer2TagMemories.remove(observer);
		super.deleteObserver(observer);
		if (countObservers() == 0 && started.compareAndSet(true, false)) {
			stop0();
		}
	}

	@Override
	public void forceStop() {
		stop0();
	}

	private void stop0() {
		logger.info("reader '{}' stopping", getReaderName());
		cleanLink(this.readerModel.getReaderSource());
		deleteObservers();
		logger.info("reader '{}' stopped", getReaderName());
	}

	private void cleanLink(ReaderSource oldReaderSource) {
		if (link != null) {
			link.deleteObserver(this);
			link.stop();
			linkManager.close(oldReaderSource);
		}
	}

	private String getReaderName() {
		return readerModel.getName();
	}

	@Override
	public void update(Observable o, Object arg) {
		if (!started.get()) {
			return;
		}
		if (!(arg instanceof OpResultSpec)) {
			logger.error("can not handle type {}", arg.getClass());
			return;
		}
		OpResultSpec result = (OpResultSpec) arg;

		// map ant -> tid -> AntennaTag not set ant
		Map<Integer, Map<String, AntennaTag>> map = new HashMap<>();
		List<TagResultSpec> tagResults = result.getTagResults();
		for (TagResultSpec tagResultSpec : tagResults) {
			logger.debug("receive {}", tagResultSpec);
			int ant = tagResultSpec.getAntennaId();
			map.putIfAbsent(ant, new HashMap<>());

			AntennaTag newTag = generateAntennaTag(tagResultSpec);
			AntennaTag oldTag = map.get(ant).get(newTag.getTid());

			if (oldTag != null) {
				oldTag.mergeStatistic(newTag.getStatistic());
			} else {
				map.get(ant).put(newTag.getTid(), newTag);
			}

		}

		ReaderTags readerTags = new ReaderTags();
		this.readerModel.getAntennas().forEach(antenna -> {
			map.getOrDefault(antenna.getAntennaNo(), Collections.emptyMap()).forEach((tid, antennaTag) -> {
				antennaTag.setAntenna(antenna);
				readerTags.add(antennaTag);
			});
		});

		setChanged();
		logger.debug("send tags " + readerTags.getTags());
		notifyObservers(readerTags);
	}

	private AntennaTag generateAntennaTag(TagResultSpec tagResultSpec) {
		AntennaTag newTag = new AntennaTag();
		newTag.setTag(generateTag(tagResultSpec));
		return newTag;
	}

	@Override
	public void updateReaderModel(ReaderModel newReaderModel) {
		boolean needRestartOp = !this.readerModel.getAntennas().equals(newReaderModel.getAntennas());
		ReaderSource oldReaderSource = this.readerModel.update(newReaderModel);
		if (started.get()) {
			if (oldReaderSource != null) {
				cleanLink(oldReaderSource);
				needRestartOp = true;
			}
			this.link = linkManager.establish(this.readerModel.getReaderSource());
			this.link.addObserver(this);
		}
		if (needRestartOp) {
			restartOpIfReaderStarted();
		}
	}

	private void restartOpIfReaderStarted() {
		if (started.get()) {
			startOp();
		}
	}

	private void startOp() {
		this.link.start(createOpspec());
	}

	@Override
	public String toString() {
		return "AbstractReader [started=" + started + ", readerModel=" + readerModel + "]";
	}

	@Override
	public String dump() {
		StringBuilder sb = new StringBuilder();
		sb.append("started=").append(started);
		sb.append(", ip=").append(readerModel.getIp());
		sb.append(", port=").append(readerModel.getPort());
		sb.append(", deviceSn=").append(readerModel.getDeviceSn());

		Set<AntennaModel> antennas = readerModel.getAntennas();
		sb.append(", Antennas:");
		for (AntennaModel e : antennas) {
			sb.append("[");
			sb.append("antennaNo=").append(e.getAntennaNo());
			sb.append(", protocol=").append(e.getProtocol());
			sb.append("]");
		}
		return sb.toString();
	}

	protected abstract OpSpec createOpspec();

	protected abstract Tag generateTag(TagResultSpec tagResultSpec);
}
